home *** CD-ROM | disk | FTP | other *** search
/ Programming Languages Suite / ProgramD2.iso / Borland / Borland C++ V5.02 / OWLSRC.PAK / UPDOWN.CPP < prev    next >
C/C++ Source or Header  |  1997-05-06  |  25KB  |  1,041 lines

  1. //----------------------------------------------------------------------------
  2. // ObjectWindows
  3. // Copyright (c) 1995, 1997 by Borland International, All Rights Reserved
  4. //
  5. //$Revision:   10.17  $
  6. //
  7. // Implementation of the TUpDown class
  8. //----------------------------------------------------------------------------
  9. #include <owl/pch.h>
  10. #if !defined(OWL_UPDOWN_H)
  11. # include <owl/updown.h>
  12. #endif
  13. #if !defined(OWL_GDIOBJEC_H)
  14. # include <owl/gdiobjec.h>
  15. #endif
  16. #if !defined(OWL_UIHELPER_H)
  17. # include <owl/uihelper.h>
  18. #endif
  19. #include <windowsx.h>
  20. #include <stdio.h>
  21.  
  22. OWL_DIAGINFO;
  23. DIAG_DECLARE_GROUP(OwlCommCtrl);        // CommonCtrl Diagnostic group
  24.  
  25. //
  26. // Constants used when ObjectWindows provides the underlying implementation of
  27. // the Up-down control...
  28. //
  29. const uint UpDownTimerID1 = 0x1000;     // Initial timer (for startup delay)
  30. const uint UpDownTimerID2 = 0x1001;     // Regular timer (for notifications)
  31. const uint InitDelay      = 500;        // Initial delay before notifying
  32. const uint RepeatDelay    = 50;         // Interval between notifications
  33.  
  34. DEFINE_RESPONSE_TABLE1(TUpDown, TControl)
  35.   EV_WM_VSCROLL,
  36.   EV_WM_HSCROLL,
  37. #if !defined(OWL_NATIVECTRL_ALWAYS)
  38.   EV_WM_ENABLE,
  39.   EV_WM_SHOWWINDOW,
  40.   EV_WM_CANCELMODE,
  41.   EV_WM_TIMER,
  42.   EV_WM_LBUTTONDOWN,
  43.   EV_WM_LBUTTONDBLCLK,
  44.   EV_WM_LBUTTONUP,
  45.   EV_WM_MOUSEMOVE,
  46. #endif
  47. END_RESPONSE_TABLE;
  48.  
  49. //
  50. // Constructor of UpDown control
  51. //
  52. TUpDown::TUpDown(TWindow* parent, int id, int x, int y, int w, int h,
  53.                  TWindow* buddy, TModule* module)
  54. :
  55.   TControl(parent, id, "", x, y, w, h, module),
  56.   Buddy(buddy),
  57.   Lower(0),
  58.   Upper(100),
  59.   Pos(0)
  60. {
  61.   Attr.Style = WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP;
  62.  
  63.   // Update flag based on availability of Common Control Library
  64.   //
  65.   NativeUse = TCommCtrl::IsAvailable() ? nuAlways : nuNever;
  66.  
  67. #if !defined(OWL_NATIVECTRL_ALWAYS)
  68.  
  69.   // When running in an environment where the system does not provide
  70.   // Common Controls we need to initialize the variables used for
  71.   // emulating a tabcontrol...
  72.   //
  73.   if (!TCommCtrl::IsAvailable()) {
  74.     Base = 10;
  75.   }
  76.  
  77. #else
  78.  
  79.   // When OWL is built with the NATIVECTRL_ALWAYS option, the
  80.   // Common Control library MUST be available....
  81.   //
  82.   CHECK(TCommCtrl::IsAvailable());
  83.  
  84. #endif
  85. }
  86.  
  87. //
  88. // Constructor to alias an up-down control which is part of a dialog
  89. // resource
  90. //
  91. TUpDown::TUpDown(TWindow* parent, int resourceId, TWindow* buddy, TModule* module)
  92. :
  93.   TControl(parent, resourceId, module),
  94.   Buddy(buddy),
  95.   Lower(0),
  96.   Upper(100),
  97.   Pos(0)
  98. {
  99.   // Update flag based on availability of Common Control Library
  100.   //
  101.   NativeUse = TCommCtrl::IsAvailable() ? nuAlways : nuNever;
  102.  
  103. #if !defined(OWL_NATIVECTRL_ALWAYS)
  104.  
  105.   // When running in an environment where the system does not provide Common
  106.   // Controls we need to initialize the variables used for emulation.
  107.   //
  108.   if (!TCommCtrl::IsAvailable()) {
  109.     Base = 10;
  110.   }
  111. #else
  112.  
  113.   // When OWL is built with the NATIVECTRL_ALWAYS option, the Common Control
  114.   // library MUST be available....
  115.   //
  116.   CHECK(TCommCtrl::IsAvailable());
  117.  
  118. #endif
  119. }
  120.  
  121. //
  122. // Return the ClassName of the underlying control.
  123. //
  124. // NOTE: The name returned depends upon whether we're on a system where the OS
  125. //       provides the underlying implementation of UPDOWN controls. Also, when
  126. //       emulating we choose to return a distinct class name; this is not
  127. //       strictly necessary with ObjectWindows. However, it facilitates
  128. //       debugging.
  129. //
  130. char far*
  131. TUpDown::GetClassName()
  132. {
  133. #if defined(OWL_NATIVECTRL_ALWAYS)
  134.  
  135.   // When OWL is built with the NATIVECTRL_ALWAYS option, the
  136.   // Common Control library MUST be available....
  137.   //
  138.   PRECONDITION(TCommCtrl::IsAvailable());
  139.   NativeUse = TNativeUse(NativeUse | nuUsing);
  140.   return UPDOWN_CLASS;
  141.  
  142. #else
  143.   // By default we'll use the native implementation if it's available
  144.   //
  145.   if (TCommCtrl::IsAvailable())
  146.     NativeUse = TNativeUse(NativeUse | nuUsing);
  147.   else
  148.     NativeUse = TNativeUse(NativeUse & ~nuUsing);
  149.  
  150.   // Return class name as per usage...
  151.   //
  152.   return TCommCtrl::IsAvailable() ? UPDOWN_CLASS : "OWL_UpDown";
  153. #endif
  154. }
  155.  
  156. //
  157. // Override to invoke the OS' 'CreateUpDownControl' method on systems where
  158. // we're using the native implementation of UpDown controls
  159. //
  160. void
  161. TUpDown::PerformCreate(int id)
  162. {
  163. #if !defined(OWL_NATIVECTRL_ALWAYS)
  164.   if (!(NativeUse & nuUsing)) {
  165.     TControl::PerformCreate(id);
  166.     return;
  167.   }
  168. #endif
  169.  
  170.   PRECONDITION(TCommCtrl::IsAvailable());
  171.   SetHandle(TCommCtrl::Dll()->CreateUpDownControl(Attr.Style,
  172.                                             Attr.X, Attr.Y, Attr.W, Attr.H,
  173.                                             Parent ? Parent->GetHandle() : 0,
  174.                                             id,
  175.                                             *GetModule(),
  176.                                             Buddy ? Buddy->GetHandle() : 0,
  177.                                             Upper, Lower, Pos));
  178. }
  179.  
  180. //
  181. // Keep TWindow from rerouting these, must be left as-is for updown control
  182. //
  183. void
  184. TUpDown::EvVScroll(uint, uint, HWND)
  185. {
  186.   DefaultProcessing();
  187. }
  188.  
  189. //
  190. // Keep TWindow from rerouting these, must be left as-is for updown control
  191. //
  192. void
  193. TUpDown::EvHScroll(uint, uint, HWND)
  194. {
  195.   DefaultProcessing();
  196. }
  197.  
  198.  
  199. #if !defined(OWL_NATIVECTRL_ALWAYS)
  200.  
  201. //
  202. // Retrieve acceleration information for the underlying up-down control
  203. //
  204. int
  205. TUpDown::GetAccel(int count, TUDAccel far* accels) const
  206. {
  207.   if (NativeUse & nuUsing) {
  208.     return (int)CONST_CAST(TUpDown*,this)->SendMessage(UDM_GETACCEL, count, TParam2(accels));
  209.   }
  210.   else {
  211.  
  212.     // This feature is not implemented by the emulation version
  213.     // of the UpDown control
  214.     //
  215.     TRACEX(OwlCommCtrl, 0, "TUpDown::GetAccel requires native \
  216.                            CommCtrl implementation");
  217.     return 0;
  218.   }
  219. }
  220.  
  221. //
  222. // Retrieve the current radix base of the underlying up-down control.
  223. // Return value is either 10 or 16.
  224. //
  225. int
  226. TUpDown::GetBase() const
  227. {
  228.   if (NativeUse & nuUsing) {
  229.     return (int)CONST_CAST(TUpDown*,this)->SendMessage(UDM_GETBASE);
  230.   }
  231.   else {
  232.     return Base;
  233.   }
  234. }
  235.  
  236. //
  237. // Return handle of buddy window of underlying up-down control
  238. //
  239. HWND
  240. TUpDown::GetBuddy() const
  241. {
  242.   if (NativeUse & nuUsing) {
  243.     return (HWND)CONST_CAST(TUpDown*,this)->SendMessage(UDM_GETBUDDY);
  244.   }
  245.   else {
  246.     return BuddyHandle;
  247.   }
  248. }
  249.  
  250. //
  251. // Return current position of underlying up-down control. The high-order word
  252. // in non-zero in case of an error. The current position is in the low-order
  253. // word.
  254. //
  255. int32
  256. TUpDown::GetPos() const
  257. {
  258.   if (NativeUse & nuUsing) {
  259.     return (int32)LoUint16(CONST_CAST(TUpDown*,this)->SendMessage(UDM_GETPOS));
  260.   }
  261.   else {
  262.     return uint32(Pos);
  263.   }
  264. }
  265.  
  266. //
  267. // Retrieve the minimum and maximum range of the underlying up-down control.
  268. // The low-order word contains the maximum position while the high-order word
  269. // contains the minimum position.
  270. //
  271. uint32
  272. TUpDown::GetRange() const
  273. {
  274.   if (NativeUse & nuUsing) {
  275.     return (uint32)CONST_CAST(TUpDown*,this)->SendMessage(UDM_GETRANGE);
  276.   }
  277.   else {
  278.     return MAKELONG(Upper, Lower);
  279.   }
  280. }
  281.  
  282. //
  283. // Retrieve the minimum and maximum range of the underlying up-down control into
  284. // the specified 'lower' and 'upper' variables respectively.
  285. //
  286. void
  287. TUpDown::GetRange(int& lower, int& upper) const
  288. {
  289.   if (NativeUse & nuUsing) {
  290.     uint32 ret = CONST_CAST(TUpDown*,this)->SendMessage(UDM_GETRANGE);
  291.     lower = HiUint16(ret);
  292.     upper = LoUint16(ret);
  293.   }
  294.   else {
  295.     lower = Lower;
  296.     upper = Upper;
  297.   }
  298. }
  299.  
  300. //
  301. // Set the acceleration of the underlying up-down control. 'count' specifies
  302. // the number of structures specified in 'accels' while the latter is the
  303. // address of an array of TUDAccel structures.
  304. //
  305. bool
  306. TUpDown::SetAccel(int count, const TUDAccel far* accels)
  307. {
  308.   if (NativeUse & nuUsing) {
  309.     return SendMessage(UDM_SETACCEL, count, TParam2(accels)) != 0;
  310.   }
  311.   else {
  312.  
  313.     // This feature is not implemented by the emulation version
  314.     // of the UpDown control
  315.     //
  316.     TRACEX(OwlCommCtrl, 0, "TUpDown::SetAccel requires native \
  317.                            CommCtrl implementation");
  318.     return false;
  319.   }
  320. }
  321.  
  322. //
  323. // Sets the radix of the underlying up-down control. The 'base' parameter
  324. // should be either '10' or '16' for decimal and hexadecimal respectively.
  325. //
  326. int
  327. TUpDown::SetBase(int base)
  328. {
  329.   if (NativeUse & nuUsing) {
  330.     return int(SendMessage(UDM_SETBASE, base));
  331.   }
  332.   else {
  333.     if (base == 10 || base == 16) {
  334.       int prevBase = Base;
  335.       Base = base;
  336.       return prevBase;
  337.     }
  338.     TRACEX(OwlCommCtrl, 0, "TUpDown::SetBase: invalid base:" << base );
  339.   }
  340.   return 0;
  341. }
  342.  
  343. //
  344. // Sets the buddy window of the underlying up-down control.
  345. //
  346. HWND
  347. TUpDown::SetBuddy(HWND hBuddy)
  348. {
  349.   if (NativeUse & nuUsing) {
  350.     return HWND(SendMessage(UDM_SETBUDDY, TParam1(hBuddy)));
  351.   }
  352.   else {
  353.     HWND oldBuddy = BuddyHandle;
  354.     BuddyHandle = hBuddy;
  355.     return oldBuddy;
  356.   }
  357. }
  358.  
  359. //
  360. // Set the current position of the underlying up-down control. The return value
  361. // is the previous position.
  362. //
  363. int32
  364. TUpDown::SetPos(int pos)
  365. {
  366.   TRACEX(OwlCommCtrl, 0, "TUpDown::SetPos(" << pos << ") "\
  367.                          "Range: (" << LOWORD(GetRange()) << ','\
  368.                                     << HIWORD(GetRange()) << ")");
  369.   if (NativeUse & nuUsing) {
  370.     return SendMessage(UDM_SETPOS, 0, MkParam2(pos, 0));
  371.   }
  372.   else {
  373.     int32 oldPos = Pos;
  374.     Pos = pos;
  375.     if (GetStyle() & UDS_SETBUDDYINT)
  376.       SetBuddyInt();
  377.     return oldPos;
  378.   }
  379. }
  380.  
  381. //
  382. // Set the minimum and maximum positions of the up-down control.
  383. //
  384. // NOTE: Neither 'lower' nor 'upper' can be greater than UD_MAXVAL or less than
  385. //       UD_MINVAL. Futhermore, the difference between the two positions must
  386. //       not exceed UD_MAXVAL;
  387. //
  388. void
  389. TUpDown::SetRange(int lower, int upper)
  390. {
  391.   PRECONDITION(lower >= UD_MINVAL);
  392.   PRECONDITION(upper >= UD_MINVAL);
  393.  
  394. #if defined(BI_PLAT_WIN32)
  395.   // The following checks are implicit in 16-bit given the range of an int
  396.   //
  397.   PRECONDITION(lower <= UD_MAXVAL);
  398.   PRECONDITION(upper <= UD_MAXVAL);
  399.   PRECONDITION(abs(upper-lower) <= UD_MAXVAL);
  400. #endif
  401.  
  402.   TRACEX(OwlCommCtrl, 0, "TUpDown::SetRange(" << lower << ',' << upper << ')'\
  403.                           << " Pos: " << GetPos() );  
  404.  
  405.   if (NativeUse & nuUsing) {
  406.     SendMessage(UDM_SETRANGE, 0, MkParam2(upper, lower));
  407.   }
  408.   else {
  409.     Lower = lower;
  410.     Upper = upper;
  411.   }
  412. }
  413.  
  414. //
  415. //
  416. //
  417. static void
  418. poorPersonsArrows(TDC& dc, TRect& rect, bool sideways, bool incr, bool prssd) 
  419. {
  420.   TRect boxRect(rect);
  421.   if (prssd)
  422.     boxRect.Offset(1, 1);
  423.  
  424.   int bh = boxRect.Height();
  425.   int bw = boxRect.Width();
  426.  
  427.   if (!sideways) {
  428.     int ydelta = incr ? (bh*2/3) : (bh+2)/3;
  429.     TPoint begPt = boxRect.TopLeft().OffsetBy((bw+1)/4, ydelta);
  430.     TPoint endPt = begPt.OffsetBy((bw+1)/2, 0);
  431.     while (begPt.x < endPt.x) {
  432.       dc.MoveTo(begPt);
  433.       dc.LineTo(endPt);
  434.       begPt.Offset( 1, incr ? -1 : 1);
  435.       endPt.Offset(-1, incr ? -1 : 1);
  436.     }
  437.   } 
  438.   else {
  439.     int xdelta = incr ? (bw+2)/3 : (bw*2/3);
  440.     TPoint begPt = boxRect.TopLeft().OffsetBy(xdelta, (bh+1)/4);
  441.     TPoint endPt = begPt.OffsetBy(0, (bh+1)/2);
  442.     while (begPt.y < endPt.y) {
  443.       dc.MoveTo(begPt);
  444.       dc.LineTo(endPt);
  445.       begPt.Offset(incr ? 1 : -1,  1);
  446.       endPt.Offset(incr ? 1 : -1, -1);
  447.     }
  448.   }
  449. }
  450.  
  451. //
  452. // Handle WM_PAINT messages - Paint control based on the state of the latter.
  453. //
  454. // NOTE: We do not have to check for 'NativeUse' here since the virtual method
  455. //       'Paint' is not called for predefined classes - i.e. it's not invoked
  456. //       when we use the Native implementation..
  457. //
  458. void
  459. TUpDown::Paint(TDC& dc, bool /*erase*/, TRect& /*rect*/)
  460. {
  461.   // Paint 'increment' rectangle
  462.   //
  463.   TRect btnRect;
  464.   GetSpinRect(btnRect, true);
  465.   bool pressed = IsSet(csIncrement) && !IsSet(csMouseOut);
  466.  
  467.   // Draw button borders
  468.   //
  469.   TUIBorder(btnRect, pressed ? TUIBorder::ButtonDn : TUIBorder::ButtonUp, 
  470.             TUIBorder::Fill).Paint(dc);
  471.  
  472.   // Draw arrows
  473.   //
  474.   poorPersonsArrows(dc, btnRect, GetStyle() & UDS_HORZ, true, pressed);
  475.  
  476.   // Paint 'decrement' rectangle
  477.   //
  478.   GetSpinRect(btnRect, false);
  479.   pressed = IsSet(csDecrement) && !IsSet(csMouseOut);
  480.  
  481.   // Draw button borders
  482.   //
  483.   TUIBorder(btnRect, pressed ? TUIBorder::ButtonDn : TUIBorder::ButtonUp, 
  484.             TUIBorder::Fill).Paint(dc);
  485.  
  486.   // Draw arrows
  487.   //
  488.   poorPersonsArrows(dc, btnRect, GetStyle() & UDS_HORZ, false, pressed);
  489. }
  490.  
  491. //
  492. // Override to initialize members when ObjectWindows provides the underlying
  493. // implementation of the UpDown control.
  494. //
  495. void
  496. TUpDown::SetupWindow()
  497. {
  498.   if (NativeUse & nuUsing)  {
  499.     TControl::SetupWindow();
  500.   }
  501.   else {
  502.     // Store away the handle to the buddy window
  503.     //
  504.     if (Buddy && Buddy->GetHandle())
  505.       BuddyHandle = Buddy->GetHandle();
  506.  
  507.     // Validate buddy
  508.     //
  509.     if (!BuddyHandle || !::IsWindow(BuddyHandle)) {
  510.       // Automatically pick previous window in Z-order as buddy
  511.       //
  512.       if (GetStyle() & UDS_AUTOBUDDY) {
  513.         BuddyHandle = GetWindow(GW_HWNDPREV);
  514.       }
  515.     }
  516.  
  517.     // Handle alignment requirements
  518.     //
  519.     if (BuddyHandle && ::IsWindow(BuddyHandle)) {
  520.       TRect budRect;
  521.       ::GetWindowRect(BuddyHandle, &budRect);
  522.       ::MapWindowPoints(HWND_DESKTOP, ::GetParent(BuddyHandle), 
  523.                         LPPOINT(&budRect.left), 2);
  524.  
  525.       if (GetStyle() & UDS_ALIGNRIGHT) {
  526.         SetWindowPos(0, budRect.right+1, budRect.top,0,0,
  527.                      SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOZORDER);
  528.       }
  529.       else if (GetStyle() & UDS_ALIGNLEFT) {
  530.         TRect udRect = GetWindowRect();
  531.         SetWindowPos(0, budRect.left-(udRect.Width()+1), budRect.top,0,0,
  532.                      SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOZORDER);
  533.       }
  534.     }
  535.  
  536.     // Don't erase background in EvEraseBkgnd, just do it in paint
  537.     //
  538.     SetBkgndColor(TColor::Transparent);
  539.   }
  540. }
  541.  
  542. //
  543. // Handle WM_ENABLE messages to allow control to paint according to its current
  544. // state..
  545. //
  546. void
  547. TUpDown::EvEnable(bool enabled)
  548. {
  549.   if (NativeUse & nuUsing) {
  550.     TControl::EvEnable(enabled);
  551.   }
  552.   else {
  553.     // Update state of control
  554.     //
  555.     if (enabled)
  556.       Clear(csGrayed);
  557.     else
  558.       Set(csGrayed);
  559.  
  560.     // Force a repaint
  561.     //
  562.     Invalidate();
  563.     UpdateWindow();
  564.   }
  565. }
  566.  
  567. //
  568. // Handle WM_SHOWWINDOW to keep track of the Window's visibility.
  569. //
  570. void
  571. TUpDown::EvShowWindow(bool show, uint status)
  572. {
  573.   if (NativeUse & nuUsing) {
  574.     TControl::EvShowWindow(show, status);
  575.   }
  576.   else {
  577.     // Update state flags
  578.     //
  579.     if (show)
  580.       Clear(csHidden);
  581.     else
  582.       Set(csHidden);
  583.   }
  584. }
  585.  
  586. //
  587. // Handle WM_CANCELMODE messages to reset current processing.
  588. //
  589. void
  590. TUpDown::EvCancelMode()
  591. {
  592.   if (NativeUse & nuUsing) {
  593.     TControl::EvCancelMode();
  594.   }
  595.   else {
  596.  
  597.     // Clear action states
  598.     //
  599.     Clear(csIncrement|csDecrement|csMouseOut);
  600.        
  601.     // Release capture
  602.     //
  603.     if (GetCapture() == *this) {
  604.       ReleaseCapture();
  605.     }
  606.  
  607.     // Kill timers, clear timer flags
  608.     //
  609.     if (IsSet(csTimer1On)) {
  610.       KillTimer(UpDownTimerID1);
  611.     }
  612.     if (IsSet(csTimer2On)) {
  613.       KillTimer(UpDownTimerID2);
  614.     }
  615.     Clear(csTimer1On|csTimer2On);
  616.   }
  617. }
  618.  
  619. //
  620. // Handle WM_TIMER messages to send periodic notifications
  621. //
  622. void
  623. TUpDown::EvTimer(uint timerId)
  624. {
  625.   if (NativeUse & nuUsing) {
  626.     TControl::EvTimer(timerId);
  627.   }
  628.   else {
  629.     // Skip the first delayed timer and set-off the repeat one
  630.     //
  631.     if (timerId == UpDownTimerID1) {
  632.       KillTimer(UpDownTimerID1);
  633.       Clear(csTimer1On);
  634.  
  635.       SetTimer(UpDownTimerID2, RepeatDelay);
  636.       Set(csTimer2On);
  637.     }
  638.  
  639.     // We're now in repeat-mode
  640.     //
  641.     if (!IsSet(csMouseOut)) {
  642.       Action();
  643.     }
  644.   }
  645. }
  646.  
  647. //
  648. // Handle WM_LBUTTONDOWN to process up/down scroll mouse requests
  649. //
  650. void
  651. TUpDown::EvLButtonDown(uint modKeys, TPoint& point)
  652. {
  653.   if (NativeUse & nuUsing) {
  654.     TControl::EvLButtonDown(modKeys, point);
  655.   }
  656.   else {
  657.     if (GetCapture() != *this) {
  658.  
  659.       // Retrieve rectangle and side activated...
  660.       //
  661.       TRect btnRect;
  662.       uint side = GetSpinRectFromPoint(btnRect, point);
  663.  
  664.       // Stop processing if we've hit the ceiling or the floor
  665.       //
  666.       if (!(GetStyle() & UDS_WRAP))
  667.         if ((side == csIncrement && Pos == Upper) ||
  668.             (side == csDecrement && Pos == Lower))
  669.           return;
  670.  
  671.       // Update Flags
  672.       //
  673.       Set(side);
  674.       Clear(csMouseOut);
  675.  
  676.       // Force repaint
  677.       //
  678.       InvalidateRect(btnRect);
  679.       UpdateWindow();
  680.  
  681.       // Update our internal 'Pos' variable from the buddy if necessary
  682.       //
  683.       if (GetStyle() & UDS_SETBUDDYINT)
  684.         GetBuddyInt();
  685.  
  686.       // Fire notifications
  687.       //
  688.       Action();
  689.  
  690.       // Hog mouse messages
  691.       //
  692.       SetCapture();
  693.  
  694.       // Set a first-crack/delayed timer
  695.       //
  696.       SetTimer(UpDownTimerID1, InitDelay);
  697.       Set(csTimer1On);
  698.     }
  699.   }
  700. }
  701.  
  702. //
  703. // Handle WM_LBUTTONDBLCLK, which we handle just like a regular LBUTTONDOWN
  704. //
  705. void
  706. TUpDown::EvLButtonDblClk(uint modKeys, TPoint& point)
  707. {
  708.   if (NativeUse & nuUsing) {
  709.     TControl::EvLButtonDblClk(modKeys, point);
  710.   }
  711.   else {
  712.     // For our purpose, a DoubleClick's just another click...
  713.     //
  714.     EvLButtonDown(modKeys, point);
  715.   }
  716. }
  717.  
  718. //
  719. // Handle WM_LBUTTONUP to reset mouse down/dblclk processing
  720. //
  721. void
  722. TUpDown::EvLButtonUp(uint modKeys, TPoint& point)
  723. {
  724.   if (NativeUse & nuUsing) {
  725.     TControl::EvLButtonUp(modKeys, point);
  726.   }
  727.   else {
  728.     // Kill timers, clear timer flags
  729.     //
  730.     if (IsSet(csTimer1On))
  731.       KillTimer(UpDownTimerID1);
  732.  
  733.     if (IsSet(csTimer2On))
  734.       KillTimer(UpDownTimerID2);
  735.  
  736.     Clear(csTimer1On|csTimer2On);
  737.  
  738.     // ReleaseCapture
  739.     //
  740.     if(GetCapture() == *this)
  741.       ReleaseCapture();
  742.  
  743.     if (IsSet(csIncrement|csDecrement) && !IsSet(csMouseOut)) {
  744.  
  745.       // Retrieve rectangle of button
  746.       //
  747.       TRect rect;
  748.       GetSpinRectFromState(rect);
  749.  
  750.       // Clear 'active' states
  751.       //
  752.       Clear(csIncrement|csDecrement|csMouseOut);
  753.  
  754.       // Force repaint of area
  755.       //
  756.       InvalidateRect(rect);
  757.       UpdateWindow();
  758.     }
  759.     else {
  760.       Clear(csIncrement|csDecrement|csMouseOut);
  761.     }
  762.   }
  763. }
  764.  
  765. //
  766. // Handle WM_MOUSEMOVE to monitor mouse location which processing mouse
  767. // down/dblclk requests.
  768. //
  769. void
  770. TUpDown::EvMouseMove(uint modKeys, TPoint& point)
  771. {
  772.   if (NativeUse & nuUsing) {
  773.     TControl::EvMouseMove(modKeys, point);
  774.   }
  775.   else {
  776.     // Check if we're in 'clicked' mode
  777.     //
  778.     if (IsSet(csIncrement) || IsSet(csDecrement)) {
  779.  
  780.       // Retrieve clicked/active rectangle
  781.       //
  782.       TRect rect;
  783.       GetSpinRectFromState(rect);
  784.  
  785.       // Save the state bits
  786.       //
  787.       uint savedState = Bits;
  788.  
  789.       // Update state based on current cursor location
  790.       //
  791.       if (rect.Contains(point))
  792.         Clear(csMouseOut);
  793.       else
  794.         Set(csMouseOut);
  795.  
  796.       // Invalidate if necessary
  797.       //
  798.       if (Bits != savedState) {
  799.         InvalidateRect(rect);
  800.         UpdateWindow();
  801.       }
  802.     }
  803.   }
  804. }
  805.  
  806. //
  807. // Retrieve the rectangle of either 'up' or 'down' button
  808. //
  809. void
  810. TUpDown::GetSpinRect(TRect& rect, bool incRect)
  811. {
  812.   // Start with client area
  813.   //
  814.   GetClientRect(rect);
  815.  
  816.   // Hit test based on style/requested side
  817.   //
  818.   if (GetStyle() & UDS_HORZ) {
  819.     if (incRect)
  820.       rect.left = rect.Width()/2;
  821.     else
  822.       rect.right = rect.Width()/2;
  823.   }
  824.   else {
  825.     if (incRect)
  826.       rect.bottom = rect.Height()/2;
  827.     else
  828.       rect.top += rect.Height()/2;
  829.   }
  830. }
  831.  
  832. //
  833. // Retrieve the rectangle of the 'active' button based on the current state of
  834. // the control.
  835. //
  836. void
  837. TUpDown::GetSpinRectFromState(TRect& rect)
  838. {
  839.   if (IsSet(csIncrement)) {
  840.     CHECK(!IsSet(csDecrement));
  841.     GetSpinRect(rect, true);
  842.   }
  843.   else {
  844.     CHECK(IsSet(csDecrement));
  845.     GetSpinRect(rect, false);
  846.   }
  847. }
  848.  
  849. //
  850. // Retrieves the rectangle of the 'active' button based on the location
  851. // specified via 'pt'. Returns either 'csIncrement' or 'csDecrement' to
  852. // indicate which area the point was in.
  853. //
  854. uint
  855. TUpDown::GetSpinRectFromPoint(TRect& rect, const TPoint& pt)
  856. {
  857.   // Start with client area
  858.   //
  859.   GetClientRect(rect);
  860.  
  861.   uint side;
  862.  
  863.   // Hit test based on style/point location
  864.   //
  865.   if (GetStyle() & UDS_HORZ) {
  866.     if (pt.x > rect.Width()/2) {
  867.       rect.left = rect.Width()/2;
  868.       side = csIncrement;
  869.     }
  870.     else {
  871.       rect.right = rect.Width()/2 + 1;
  872.       side = csDecrement;
  873.     }
  874.   }
  875.   else {
  876.     if (pt.y > rect.Height()/2) {
  877.       rect.top += rect.Height()/2;
  878.       side = csDecrement;
  879.     }
  880.     else {
  881.       rect.bottom = rect.Height()/2 + 1;
  882.       side = csIncrement;
  883.     }
  884.   }
  885.   return side;
  886. }
  887.  
  888. //
  889. // Send UP or DOWN notifications
  890. //
  891. void
  892. TUpDown::Action()
  893. {
  894.   // Compute delta based on flags
  895.   //
  896.   // NOTE: This method automatically handles 'wrapping' since it's *NOT* called
  897.   //       if we've reached a rangeLimit and are not in 'wrap' mode.
  898.   //
  899.   int delta;
  900.   if (IsSet(csIncrement)) {
  901.     // Increment implies 'Pos' gravitates towards 'Upper'
  902.     //
  903.     if (Lower < Upper && Pos < Upper)
  904.       delta = 1;
  905.     else if (Lower > Upper && Pos > Upper)
  906.       delta = -1;
  907.     else if (Pos == Upper)
  908.       delta = (Upper > Lower) ? (Lower - Upper) : (Upper - Lower);
  909.   }
  910.   else {
  911.     // Decrement implies 'Pos' gravitates towards 'Lower'
  912.     //
  913.     if (Lower < Upper && Pos > Lower)
  914.       delta = -1;
  915.     else if (Lower > Upper && Pos < Lower)
  916.       delta = 1;
  917.     else if (Pos == Lower)
  918.       delta = (Upper > Lower) ? (Upper - Lower) : (Lower - Upper);
  919.   }
  920.  
  921.   // Send UDN_DELTAPOS notification to parent
  922.   //
  923.   TNmUpDown info(*this, Attr.Id, UDN_DELTAPOS, Pos, delta);
  924.   if (Parent && Parent->GetHandle()) {
  925.  
  926.     // Stop if parent vetoed change
  927.     //
  928.     if (Parent->SendNotification(Attr.Id, info) != 0)
  929.       return;
  930.   }
  931.  
  932.   // Adjust position using delta (potentially modified by parent)
  933.   //
  934.   Pos += info.iDelta;
  935.  
  936.   // Handle Buddy
  937.   //
  938.   if (::IsWindow(BuddyHandle)) {
  939.  
  940.     // Set control's text
  941.     //
  942.     if (GetStyle() & UDS_SETBUDDYINT) {
  943.       SetBuddyInt();
  944.     }
  945.     else {
  946.  
  947.       // Shoot notification to buddy
  948.       //
  949.       int code = IsSet(csIncrement) ? SB_LINEUP : SB_LINEDOWN;
  950.       FORWARD_WM_VSCROLL(BuddyHandle, GetHandle(), code, Pos, ::SendMessage);
  951.     }
  952.   }
  953. }
  954.  
  955. //
  956. // Update buddy's caption based on current position
  957. //
  958. void
  959. TUpDown::SetBuddyInt() const
  960. {
  961.   PRECONDITION(GetStyle() & UDS_SETBUDDYINT);
  962.  
  963.   if (::IsWindow(BuddyHandle)) {
  964.     // Make text out of pos
  965.     //
  966.     char txt1[20];
  967.     char *pTxt = txt1;
  968.  
  969.     if (Base == 16)
  970.       sprintf(txt1, "%X", Pos);
  971.     else
  972.       sprintf(txt1, "%d", Pos);
  973.  
  974.     // Put in commas, unless 'UDS_NOTHOUSANDS'
  975.     //
  976.     char txt2[20];
  977.  
  978.     if (!(GetStyle() & UDS_NOTHOUSANDS)) {
  979.       // Don't bother if the string's len than 3 characters long
  980.       //
  981.       int len = strlen(txt1);
  982.       if (len > 3) {
  983.         int i = len % 3;
  984.         char* p = txt2;
  985.         for (int j = 0; j < len; j++) {
  986.           if (j % 3 == i)
  987.             *p++ = ',';
  988.           *p++ = *pTxt++;
  989.         }
  990.  
  991.         // Copy 0 terminator
  992.         //
  993.         *p = *pTxt;
  994.  
  995.         // Point to buffer with commas
  996.         //
  997.         pTxt = txt2;
  998.       }
  999.     }
  1000.  
  1001.     // Set text via 'WM_SETTEXT'...
  1002.     //
  1003.     ::SetWindowText(BuddyHandle, pTxt);
  1004.   }
  1005. }
  1006.  
  1007. //
  1008. // Retrieve current position from buddy's caption
  1009. //
  1010. void
  1011. TUpDown::GetBuddyInt()
  1012. {
  1013.   PRECONDITION(GetStyle() & UDS_SETBUDDYINT);
  1014.  
  1015.   if (::IsWindow(BuddyHandle)) {
  1016.  
  1017.     // Make text out of pos
  1018.     //
  1019.     char txt1[20];
  1020.     if (::GetWindowText(BuddyHandle, txt1, sizeof(txt1))) {
  1021.  
  1022.       // Strip commas
  1023.       //
  1024.       char txt2[20];
  1025.       char *src = txt1, *dst = txt2;
  1026.       do {
  1027.         if (*src != ',')
  1028.           *dst++ = *src;
  1029.       } while(*src++);
  1030.  
  1031.       // Validate & Update internal variable
  1032.       //
  1033.       long newPos = strtol(txt2, 0, Base == 16 ? 16 : 10);
  1034.       if (newPos && newPos >= Lower && newPos <= Upper)
  1035.         Pos = int(newPos);
  1036.     }
  1037.   }
  1038. }
  1039.  
  1040. #endif  //  !OWL_NATIVECTRL_ALWAYS
  1041.